﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Devsoft.Utilities
{
	public class StringTokenizer
	{
		private string _value;
		private string delimiters;

		private bool returnDelimiters;

		private int position;

		/**
		 * Constructs a new {@code StringTokenizer} for the parameter string using
		 * whitespace as the delimiter. The {@code returnDelimiters} flag is set to
		 * {@code false}.
		 * 
		 * @param string
		 *            the string to be tokenized.
		 */
		public StringTokenizer(string value)
			: this(value, " \t\n\r\f", false) //$NON-NLS-1$
		{
		}

		/**
		 * Constructs a new {@code StringTokenizer} for the parameter string using
		 * the specified delimiters. The {@code returnDelimiters} flag is set to
		 * {@code false}. If {@code delimiters} is {@code null}, this constructor
		   * doesn't throw an {@code Exception}, but later calls to some methods might
		 * throw a {@code NullPointerException}.
		  * 
		   * @param string
		   *            the string to be tokenized.
		   * @param delimiters
		   *            the delimiters to use.
		   */
		public StringTokenizer(string value, string delimiters)
			: this(value, delimiters, false)
		{
		}

		/**
	   * Constructs a new {@code StringTokenizer} for the parameter string using
		 * the specified delimiters, returning the delimiters as tokens if the
		 * parameter {@code returnDelimiters} is {@code true}. If {@code delimiters}
		 * is null this constructor doesn't throw an {@code Exception}, but later
		 * calls to some methods might throw a {@code NullPointerException}.
		 * 
		 * @param string
	   *            the string to be tokenized.
	   * @param delimiters
	   *            the delimiters to use.
	   * @param returnDelimiters
	   *            {@code true} to return each delimiter as a token.
	   */
		public StringTokenizer(string value, string delimiters,
				bool returnDelimiters)
		{
			if (value != null)
			{
				this._value = value;
				this.delimiters = delimiters;
				this.returnDelimiters = returnDelimiters;
				this.position = 0;
			}
			else
				throw new ArgumentNullException();
		}

		/**
		 * Returns the number of unprocessed tokens remaining in the string.
		 * 
		 * @return number of tokens that can be retreived before an {@code
		 *         Exception} will result from a call to {@code nextToken()}.
		 */
		public int CountTokens()
		{
			int count = 0;
			bool inToken = false;
			for (int i = position, length = _value.Length; i < length; i++)
			{
				if (delimiters.IndexOf(_value[i], 0) >= 0)
				{
					if (returnDelimiters)
						count++;
					if (inToken)
					{
						count++;
						inToken = false;
					}
				}
				else
				{
					inToken = true;
				}
			}
			if (inToken)
				count++;
			return count;
		}

		/**
		 * Returns {@code true} if unprocessed tokens remain. This method is
		 * implemented in order to satisfy the {@code Enumeration} interface.
		 * 
		 * @return {@code true} if unprocessed tokens remain.
		 */
		public bool HasMoreElements()
		{
			return HasMoreTokens();
		}

		/**
		 * Returns {@code true} if unprocessed tokens remain.
		   * 
		   * @return {@code true} if unprocessed tokens remain.
		   */
		public bool HasMoreTokens()
		{
			if (delimiters == null)
			{
				throw new ArgumentNullException();
			}
			int length = _value.Length;
			if (position < length)
			{
				if (returnDelimiters)
					return true; // there is at least one character and even if
				// it is a delimiter it is a token

				// otherwise find a character which is not a delimiter
				for (int i = position; i < length; i++)
					if (delimiters.IndexOf(_value[i], 0) == -1)
						return true;
			}
			return false;
		}

		/**
		 * Returns the next token in the string as an {@code Object}. This method is
		 * implemented in order to satisfy the {@code Enumeration} interface.
		 * 
			* @return next token in the string as an {@code Object}
		  * @throws NoSuchElementException
	   *                if no tokens remain.
			*/
		public Object NextElement()
		{
			return NextToken();
		}

		public Object Current
		{
			get
			{
				return NextToken();
			}
		}

		/**
		 * Returns the next token in the string as a {@code String}.
		 * 
	   * @return next token in the string as a {@code String}.
	* @throws NoSuchElementException
	*                if no tokens remain.
		 */
		public String NextToken()
		{
			if (delimiters == null)
			{
				throw new ArgumentNullException();
			}
			int i = position;
			int length = _value.Length;

			if (i < length)
			{
				if (returnDelimiters)
				{
					if (delimiters.IndexOf(_value[position], 0) >= 0)
						return string.Concat(_value[position++]);
					for (position++; position < length; position++)
						if (delimiters.IndexOf(_value[position], 0) >= 0)
							return _value.Substring(i, position);
					return _value.Substring(i);
				}

				while (i < length && delimiters.IndexOf(_value[i], 0) >= 0)
					i++;
				position = i;
				if (i < length)
				{
					for (position++; position < length; position++)
						if (delimiters.IndexOf(_value[position], 0) >= 0)
							return _value.Substring(i, Math.Min(position, _value.Length - i - 1));
					return _value.Substring(i);
				}
			}
			throw new IndexOutOfRangeException();
		}

		public String NextWord()
		{
			if (delimiters == null)
			{
				throw new ArgumentNullException();
			}
			int i = position;
			int length = _value.Length;

			if (i < length)
			{
				while (i < length && delimiters.IndexOf(_value[i], 0) >= 0)
					i++;
				position = i;
				if (i < length)
				{
					for (position++; position < length; position++)
						if (delimiters.IndexOf(_value[position], 0) >= 0)
							return _value.Substring(i, position - i);
					return _value.Substring(i);
				}
			}
			throw new IndexOutOfRangeException();
		}

		/**
		 * Returns the next token in the string as a {@code String}. The delimiters
		 * used are changed to the specified delimiters.
		 * 
		 * @param delims
		 *            the new delimiters to use.
		  * @return next token in the string as a {@code String}.
		  * @throws NoSuchElementException
	   *                if no tokens remain.
	   */
		public string NextToken(string delims)
		{
			this.delimiters = delims;
			return NextToken();
		}
	}
}
